﻿// Copyright (c) 2009, Tom Lokovic
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

using System;
using System.ComponentModel;
using SharpDX.Multimedia;
using SharpDX.XAudio2;
using ScheduledXAudio2.Structures;

namespace ScheduledXAudio2
{
    /// <summary>
    /// Base class for all messages.
    /// </summary>
    public abstract class Message
    {
        /// <summary>
        /// Protected constructor.
        /// </summary>
        /// <param name="time">The timestamp for this message.</param>
        protected Message(float time)
        {
            this.time = time;
        }

        /// <summary>
        /// Sends this message immediately.
        /// </summary>
        public abstract void SendNow();

        /// <summary>
        /// Returns a copy of this message, shifted in time by the specified amount.
        /// </summary>
        public abstract Message MakeTimeShiftedCopy(float delta);

        /// <summary>
        /// Milliseconds since the music started.
        /// </summary>
        public float Time { get { return time; } }
        private float time;
    }

    /// <summary>
    /// Base class for messages relevant to a specific device.
    /// </summary>
    public abstract class DeviceMessage : Message
    {
        /// <summary>
        /// Protected constructor.
        /// </summary>
        protected DeviceMessage(DeviceBase device, float time)
            : base(time)
        {
            if (device == null)
            {
                throw new ArgumentNullException("device");
            }
            this.device = device;
        }

        /// <summary>
        /// The device from which this message originated, or for which it is destined.
        /// </summary>
        public DeviceBase Device
        {
            get
            {
                return device;
            }
        }
        private DeviceBase device;
    }

    #region Clock level messages

    public abstract class ClockMessage : Message, INotifyPropertyChanged
    {
        public ClockMessage(Clock clock, float time) : base(time)
        {
            this.clock = clock;
        }

        public Clock Clock
        {
            get
            { return clock; }
        }
        private Clock clock;

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        internal void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {

                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion

        
    }
    public class ClockEndedNotifier : ClockMessage
    { 
    public ClockEndedNotifier(Clock clock, float time) : base(clock, time)
    {
        
    }

        public override void  SendNow()
        {
            NotifyPropertyChanged("ClockEnded");
        }

        /// <summary>
        /// Returns a copy of this message, shifted in time by the specified amount.
        /// </summary>
        public override Message MakeTimeShiftedCopy(float delta)
        {
            return new ClockEndedNotifier(Clock, Time + delta);
        }

    }

    

    public class BPMChangeMessage : ClockMessage
    {
        public BPMChangeMessage(Clock clock, int newValue, float time)
            : base(clock, time)
    {
        NewValue = newValue;
    }

    private int NewValue { get; set; }

    public override void SendNow()
    {
        Clock.BeatsPerMinute = NewValue;
    }

    public override Message MakeTimeShiftedCopy(float delta)
    {
        return new BPMChangeMessage(Clock, NewValue, Time + delta);
    }
    }

    /// <summary>
    /// Message intended to be sent by the Engine to tell the clock to start.
    /// </summary>
    public class ClockStartMessage : ClockMessage
    {
        public ClockStartMessage(Clock clock, float time)
            : base(clock, time)
        { }

        public override void SendNow()
        {
            Clock.Start();
        }
        public override Message MakeTimeShiftedCopy(float delta)
        {
            return new ClockStartMessage(Clock, Time + delta);
        }
    }    

    #endregion

    #region Submixer level messages

    /// <summary>
    /// Base class for messages relevant to a specific device channel.
    /// </summary>
    public abstract class ChannelMessage : DeviceMessage
    {
        /// <summary>
        /// Protected constructor.
        /// </summary>
        protected ChannelMessage(DeviceBase device, Channel channel, float time)
            : base(device, time)
        {
            channel.Validate();
            this.channel = channel;
        }

        /// <summary>
        /// Channel.
        /// </summary>
        public Channel Channel { get { return channel; } }
        private Channel channel;
    }

    /// <summary>
    /// SubmixerMessage is the same as ChannelMessage.
    /// </summary>
    public abstract class SubmixerMessage : DeviceMessage
    {
        /// <summary>
        /// Protected constructor.
        /// </summary>
        protected SubmixerMessage(DeviceBase device, Channel channel, float time)
            : base(device, time)
        {
            channel.Validate();
            this.channel = channel;
        }

        /// <summary>
        /// Channel.
        /// </summary>
        public Channel Channel { get { return channel; } }
        private Channel channel;
    }



    #endregion

    #region AudioBuffer/WaveFormat level messages

    /// <summary>
    /// Base class for messages relevant to a specific note.
    /// </summary>
    public abstract class NoteMessage : ChannelMessage
    {
        /// <summary>
        /// Protected constructor.
        /// </summary>
        protected NoteMessage(DeviceBase device, Channel channel, float time)
            : base(device, channel, time)
        {
           
            
        }
    }

    /// <summary>
    /// a message that tells the device to be silent.  represents a rest in music theory.
    /// </summary>
    public class RestMessage : NoteMessage
    { 
    protected RestMessage(DeviceBase device, Channel channel, float time) : base(device, channel, time)
    {}
    public override void SendNow()
    {
        throw new NotImplementedException();
    }
    public override Message MakeTimeShiftedCopy(float delta)
    {
        return new RestMessage(Device, Channel, Time + delta);
    }
    }

    #endregion

    #region Sound Instance level messages

    public class DrumMessage : NoteMessage
    {
        public DrumMessage(DrumPatchHost device, Channel channel, AudioBuffer buffer, WaveFormat format, VoiceSendDescriptor vsd,
           float time)
            : base(device, channel, time)
        {
            Buffer = buffer;
           Format = format;
           Vsd = vsd;
            //this.device = device;
        }

        public DrumMessage(DrumPatchHost device, Channel channel, SimpleUnit unit, float time) : base(device, channel, time)
        {
            Unit = unit;
        }

        //DrumPatchHost device;
        /// <summary>
        /// Sends this message immediately.
        /// </summary>
        public override void SendNow()
        {
            //((DrumPatchHost)Device).SendDrumNote(Unit);
            if (!((DrumPatchHost)Device).SendDrumNote(Unit))
            {
                ((DrumPatchHost)Device).SendDrumNote(Vsd, Format, Buffer);
            }
        }
       

        /// <summary>
        /// Returns a copy of this message, shifted in time by the specified amount.
        /// </summary>
        public override Message MakeTimeShiftedCopy(float delta)
        {
            return new DrumMessage((DrumPatchHost)Device, Channel, Buffer, Format, Vsd, Time + delta);
        }
        public VoiceSendDescriptor Vsd { get; private set; }
        public WaveFormat Format { get; private set; }
        public AudioBuffer Buffer { get; private set; }

        public SimpleUnit Unit { get; private set; }

    }

  

    #endregion
    /// <summary>
    /// Pseudo-MIDI message used to arrange for a callback at a certain time.
    /// </summary>
    /// <remarks>
    /// <para>This message can be scheduled with <see cref="Clock.Schedule(Message)">
    /// Clock.Schedule</see> just like any other message.  When its time comes and it
    /// gets "sent", it invokes the callback provided in the constructor.</para>
    /// <para>The idea is that you can embed callback points into the music you've
    /// scheduled, so that (if the clock gets to that point in the music) your code has
    /// an opportunity for some additional processing.</para>
    /// <para>The callback is invoked on the MidiOutputDevice's worker thread.</para>
    /// </remarks>
    public class CallbackMessage : Message
    {
        /// <summary>
        /// Delegate called when a CallbackMessage is sent.
        /// </summary>
        /// <param name="time">The time at which this event was scheduled.</param>
        /// <returns>Additional messages which should be scheduled as a result of this callback,
        /// or null.</returns>
        public delegate void CallbackType(float time);

        /// <summary>
        /// Constructs a Callback message.
        /// </summary>
        /// <param name="callback">The callback to invoke when this message is "sent".</param>
        /// <param name="time">The timestamp for this message.</param>
        public CallbackMessage(Delegate callback, int barIndex, float time)
            : base(time)
        {
            this.callback = callback;
            this.barIndex = barIndex;
        }

        /// <summary>
        /// The callback to invoke when this message is "sent".
        /// </summary>
        public Delegate Callback { get { return callback; } }
        private Delegate callback;
        private Object locker = new Object();
        private int barIndex;

        /// <summary>
        /// Sends this message immediately, ignoring the beatTime.
        /// </summary>
        public override void SendNow()
        {
            if (callback != null)
                //try
                //{
                // callback.DynamicInvoke(barIndex);
                callback.DynamicInvoke();
                //}
                ////occurs when a bar is added while this callback is invoking.
                //catch (System.Reflection.TargetInvocationException)
                //{                    
                //    callback.DynamicInvoke(barIndex - 1);
                //    barIndex -= 1;
                //}
               
        }

        /// <summary>
        /// Returns a copy of this message, shifted in time by the specified amount.
        /// </summary>
        public override Message MakeTimeShiftedCopy(float delta)
        {
            return new CallbackMessage(callback, barIndex, Time + delta);
        }
    }
}
